/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.io;

import com.android.dx.util.ByteInput;
import com.android.dx.util.Leb128Utils;

/**
 * SAX-style reader for encoded values. TODO: convert this to a pull-style
 * reader
 */
public class EncodedValueReader {

	public static final int ENCODED_BYTE = 0x00;
	public static final int ENCODED_SHORT = 0x02;
	public static final int ENCODED_CHAR = 0x03;
	public static final int ENCODED_INT = 0x04;
	public static final int ENCODED_LONG = 0x06;
	public static final int ENCODED_FLOAT = 0x10;
	public static final int ENCODED_DOUBLE = 0x11;
	public static final int ENCODED_STRING = 0x17;
	public static final int ENCODED_TYPE = 0x18;
	public static final int ENCODED_FIELD = 0x19;
	public static final int ENCODED_ENUM = 0x1b;
	public static final int ENCODED_METHOD = 0x1a;
	public static final int ENCODED_ARRAY = 0x1c;
	public static final int ENCODED_ANNOTATION = 0x1d;
	public static final int ENCODED_NULL = 0x1e;
	public static final int ENCODED_BOOLEAN = 0x1f;

	protected final ByteInput in;

	public EncodedValueReader(ByteInput in) {
		this.in = in;
	}

	public EncodedValueReader(EncodedValue in) {
		this(in.asByteInput());
	}

	public final void readArray() {
		int size = Leb128Utils.readUnsignedLeb128(in);
		visitArray(size);

		for (int i = 0; i < size; i++) {
			readValue();
		}
	}

	public final void readAnnotation() {
		int typeIndex = Leb128Utils.readUnsignedLeb128(in);
		int size = Leb128Utils.readUnsignedLeb128(in);
		visitAnnotation(typeIndex, size);

		for (int i = 0; i < size; i++) {
			visitAnnotationName(Leb128Utils.readUnsignedLeb128(in));
			readValue();
		}
	}

	public final void readValue() {
		int argAndType = in.readByte() & 0xff;
		int type = argAndType & 0x1f;
		int arg = (argAndType & 0xe0) >> 5;
		int size = arg + 1;

		switch (type) {
		case ENCODED_BYTE:
		case ENCODED_SHORT:
		case ENCODED_CHAR:
		case ENCODED_INT:
		case ENCODED_LONG:
		case ENCODED_FLOAT:
		case ENCODED_DOUBLE:
			visitPrimitive(argAndType, type, arg, size);
			break;
		case ENCODED_STRING:
			visitString(type, readIndex(in, size));
			break;
		case ENCODED_TYPE:
			visitType(type, readIndex(in, size));
			break;
		case ENCODED_FIELD:
		case ENCODED_ENUM:
			visitField(type, readIndex(in, size));
			break;
		case ENCODED_METHOD:
			visitMethod(type, readIndex(in, size));
			break;
		case ENCODED_ARRAY:
			visitArrayValue(argAndType);
			readArray();
			break;
		case ENCODED_ANNOTATION:
			visitAnnotationValue(argAndType);
			readAnnotation();
			break;
		case ENCODED_NULL:
			visitEncodedNull(argAndType);
			break;
		case ENCODED_BOOLEAN:
			visitEncodedBoolean(argAndType);
			break;
		}
	}

	protected void visitArray(int size) {
	}

	protected void visitAnnotation(int typeIndex, int size) {
	}

	protected void visitAnnotationName(int nameIndex) {
	}

	protected void visitPrimitive(int argAndType, int type, int arg, int size) {
		for (int i = 0; i < size; i++) {
			in.readByte();
		}
	}

	protected void visitString(int type, int index) {
	}

	protected void visitType(int type, int index) {
	}

	protected void visitField(int type, int index) {
	}

	protected void visitMethod(int type, int index) {
	}

	protected void visitArrayValue(int argAndType) {
	}

	protected void visitAnnotationValue(int argAndType) {
	}

	protected void visitEncodedBoolean(int argAndType) {
	}

	protected void visitEncodedNull(int argAndType) {
	}

	private int readIndex(ByteInput in, int byteCount) {
		int result = 0;
		int shift = 0;
		for (int i = 0; i < byteCount; i++) {
			result += (in.readByte() & 0xff) << shift;
			shift += 8;
		}
		return result;
	}
}
